//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.cache
{


///
/// <summary> * Simple implementation of Least-recently-used cache.
/// * 
/// * The cache has a <CODE>maxSize</CODE> set and when the number of cached
/// * elements exceeds that limit the least recently used element will be removed. </summary>
/// 
	public class LruCache<K, E> : Cache<K, E>
	{
		private readonly string name;
		internal int maxSize = 1000;
		private bool resizing = false;
		private bool adaptive = false;

		private readonly AdaptiveCacheManager cacheManager;

		private Map<K, E> cache = new LinkedHashMap<K, E>(500, 0.75f, true)
		{
			protected bool removeEldestEntry(Map.Entry<K, E> eldest)
			{
			// synchronization miss with old value on maxSize here is ok
				if (base.size() > maxSize)
				{
					if (isAdaptive() && !isResizing())
					{
						adaptCache();
					}
					else
					{
						base.Remove(eldest.Key);
						elementCleaned(eldest.Value);
					}
				}
				return false;
			}
		}

		void adaptCache()
		{
			if (cacheManager != null)
			{
				cacheManager.adaptCache(this);
			}
		}

//    public void setMaxSize( int maxSize )
//    {
//        this.maxSize = maxSize;
//    }

///    
///     <summary> * Creates a LRU cache. If <CODE>maxSize < 1</CODE> an
///     * IllegalArgumentException is thrown.
///     *  </summary>
///     * <param name="name">
///     *            name of cache </param>
///     * <param name="maxSize">
///     *            maximum size of this cache </param>
///     * <param name="cacheManager">
///     *            adaptive cache manager or null if adaptive caching not needed </param>
///     
		public LruCache(string name, int maxSize, AdaptiveCacheManager cacheManager)
		{
			this.cacheManager = cacheManager;
			if (name == null || maxSize < 1)
			{
				throw new IllegalArgumentException("maxSize=" + maxSize + ", name=" + name);
			}
			this.name = name;
			this.maxSize = maxSize;
		}

		public string Name
		{
			return this.name;
		}

		public synchronized void put(K key, E element)
		{
			if (key == null || element == null)
			{
				throw new IllegalArgumentException("key=" + key + ", element=" + element);
			}
			cache.put(key, element);
		}

		public synchronized E Remove(K key)
		{
			if (key == null)
			{
				throw new IllegalArgumentException("Null parameter");
			}
			return cache.Remove(key);
		}

		public synchronized E @get(K key)
		{
			if (key == null)
			{
				throw new IllegalArgumentException();
			}
			return cache.get(key);
		}

		public synchronized void Clear()
		{
			resizeInternal(0);
		}

		public synchronized int size()
		{
			return cache.size();
		}

///    
///     <summary> * Returns the maximum size of this cache.
///     *  </summary>
///     * <returns> maximum size </returns>
///     
		public int maxSize()
		{
			return maxSize;
		}

///    
///     <summary> * Changes the max size of the cache. If <CODE>newMaxSize</CODE> is
///     * greater then <CODE>maxSize()</CODE> next invoke to <CODE>maxSize()</CODE>
///     * will return <CODE>newMaxSize</CODE> and the entries in cache will not
///     * be modified. 
///     * <p>
///     * If <CODE>newMaxSize</CODE> is less then <CODE>size()</CODE>
///     * the cache will shrink itself removing least recently used element until
///     * <CODE>size()</CODE> equals <CODE>newMaxSize</CODE>. For each element
///     * removed the <seealso cref="#elementCleaned"/> method is invoked.
///     * <p>
///     * If <CODE>newMaxSize</CODE> is less then <CODE>1</CODE> an
///     * <seealso cref="IllegalArgumentException"/> is thrown.
///     *  </summary>
///     * <param name="newMaxSize">
///     *            the new maximum size of the cache </param>
///     
		public synchronized void resize(int newMaxSize)
		{
			if (newMaxSize < 1)
			{
				throw new IllegalArgumentException("newMaxSize=" + newMaxSize);
			}
			resizeInternal(newMaxSize);
		}

		private void resizeInternal(int newMaxSize)
		{
			resizing = true;
			try
			{
				if (newMaxSize >= size())
				{
					maxSize = newMaxSize;
				}
				else if (newMaxSize == 0)
				{
					cache.Clear();
				}
				else
				{
					maxSize = newMaxSize;
					java.util.IEnumerator <Map.Entry<K, E>> itr = cache.entrySet().GetEnumerator();
					while (itr.MoveNext() && cache.size() > maxSize)
					{
						E element = itr.Current.Value;
						itr.Remove();
						elementCleaned(element);
					}
				}
			}
			finally
			{
				resizing = false;
			}
		}

		bool isResizing()
		{
			return resizing;
		}

		public void elementCleaned(E element)
		{
		}

		public bool isAdaptive()
		{
			return adaptive;
		}

		public void setAdaptiveStatus(bool status)
		{
			this.adaptive = status;
		}
	}

}